package avriServer.channel;

import java.util.HashMap;

import avriServer.client.NonexistantWaiterException;
import avriServer.client.Waiter;
import avriServer.json.JSONException;
import avriServer.server.Dispatcher;
import avriServer.server.ListChannel;

import java.util.Map.Entry;
import java.util.regex.Pattern;

public class Channel {
	/*public final static int NUMBER_MESSAGE_SAVED = 25;
	public final static HashMap<String, Action> ACTION_ALLOWED = new HashMap<String, Action>();
	*/
	public final static long KEEPALIVE_CHANNEL = 3600*14; //Time before removing inactif channel

	private final HashMap<String, Waiter> waiters = new HashMap<String, Waiter>();
	private ListChannel listChannel = new ListChannel();
	private String name ;
	private String absoluteName ;
	private Channel parentChannel = null ;
    private static Pattern pattern = Pattern.compile("\\W/");
        
    /**
     * Set the pattern which is used durint the creation of the Channel.
     * The pattern will match the _nameOrAbsoluteName of the channel before its creation.
     * e.g: if (Channel.pattern.matcher(_nameOrAbsoluteName).find()) throw new WrongChannelNameException();
     * Default to \W
     * @param _pattern
     */
	public static void SET_CHANNEL_NAME_PATTERN (Pattern _pattern) {
        Channel.pattern = _pattern;
    }
	
	public static void IS_VALID_NAME (String _nameOrAbsoluteName) throws WrongChannelNameException {
        if (Channel.pattern.matcher(_nameOrAbsoluteName).find())
            throw new WrongChannelNameException();
    }
	
	/**
	 * @param _channelAbsoluteName
	 * @return the root channel name. e.g:
	 * <code>
     *       GET_CHANNEL_NAME("general/myChannel/channel1") // return channel1 !
     *</code>
	 */
	public static String GET_CHANNEL_NAME (String _channelAbsoluteName) {
		int indexOfSeparator = _channelAbsoluteName.lastIndexOf("/") ;
		if ( indexOfSeparator != -1)
			return _channelAbsoluteName.substring(indexOfSeparator+1, _channelAbsoluteName.length());
		else 
			return _channelAbsoluteName;
    }
	/**
	 * @param _channelAbsoluteName
	 * @return the first channel. e.g: 
	 * <code>
     *       GET_ROOT_CHANNEL_NAME("general/myChannel/channel1") // return general !
     *</code>
	 */
	public static String GET_ROOT_CHANNEL_NAME (String _channelAbsoluteName) {
		int indexOfSeparator = _channelAbsoluteName.indexOf('/');
		if (indexOfSeparator == -1)
			return _channelAbsoluteName;		
		else 
			return _channelAbsoluteName.substring(0,indexOfSeparator);
	}

	/**
	 * Create a Channel with its name.
	 * Can be the absoluteName (e.g: general/myChannel/channel1 or the simple name: /channel1
	 * If it's an absoluteName, the absoluteName is parsed and the name is defined.
	 * In all cases, if you just specifie the simple name, the absoluteName will automatically be redefined when you add the Channel into another Channel.
         * _nameOrAbsoluteName is match using the pattern Channel.pattern. If the matcher is find, then throw a WrongChannelNameException
         *  e.g: if (Channel.pattern.matcher(_nameOrAbsoluteName).find()) throw new WrongChannelNameException();
	 * @param _name
	 * @throws WrongChannelNameException 
         * @throws WrongChannelNameException
	 */
	public Channel (String _nameOrAbsoluteName) throws WrongChannelNameException{
		if (_nameOrAbsoluteName == null) 
			throw new WrongChannelNameException();
        if(_nameOrAbsoluteName.substring(_nameOrAbsoluteName.length()-1, _nameOrAbsoluteName.length()).equals("/"))
            _nameOrAbsoluteName = _nameOrAbsoluteName.substring(0,_nameOrAbsoluteName.length()-1);
        Channel.IS_VALID_NAME(_nameOrAbsoluteName);
        this.name = Channel.GET_CHANNEL_NAME(_nameOrAbsoluteName);
		this.absoluteName = _nameOrAbsoluteName;
	}
	
	
	/**
	 * Add the Waiter _waiter to this channel.
	 * It also automatically add to the _waiter's channel list this channel.
	 * So a waiter directly knows on which channels he has subscribe
	 * @param _waiter
	 */
	public void addWaiter (Waiter _waiter) {
		this.waiters.put(_waiter.getPublicId(), _waiter) ;
		_waiter.subscribe(this);
	}
	
	/**
	 * @param _waiterPublicId
	 * @return the waiter which has the same name than _waiterPublicId
	 * @throws UserDisconnectedException 
	 */
	public Waiter getWaiter (String _waiterPublicId) throws NonexistantWaiterException {
		if (!this.waiters.containsKey(_waiterPublicId))
			throw new NonexistantWaiterException();
		return this.waiters.get(_waiterPublicId);
	}
	
	/**
	 * @return the identifier of the channel. The identifier is the absolute path of the channel e.g: general/myChannel/channel1
	 */
	public String getAbsoluteName() {
		return this.absoluteName;
	}
	
	/**
	 * The identifier is the relative path of the channel e.g: if getIdentifier() == general/myChannel/channel1
	 * then getName will return "channel1"
	 * @return the name of the channel.
	 */
	public String getName() {
		return this.name;
	}
	
	/**
	 * Set the absolute name.
	 * Be carefull. This is a UNSAFE operation. 
	 * Should only be handled by the ListChannel Object or the Dispatcher.
	 * @param _absoluteName
	 */
	public void setAbsoluteName (String _absoluteName) {
		this.absoluteName = _absoluteName;
	}
	
	/**
	 * Return the specific Channel Object.<br />
	 * absoluteName of the channel _channel is used as the relative path.<br />
     * e.g: "general/myChannel/channel1"<br />
     * <code>
     *       Channel generalChannel = Dispatcher.getChannel(Channel.create("general"));<br />
     *       generalChannel.getChannel("myChannel") ; // return the channel "general/myChannel"<br />
     *       generalChannel.getChannel("myChannel/channel1") ; // return the channel "general/myChannel/channel1"<br />
     * </code>
     * Notice that the Dispatcher can do the same thing synchronized.<p>
	 * @param _channelName: the name of the channel<br />
	 * @return the channel with path = _channelName<br />
	 * @throws NonexistantChannelException <br />
	 * @throws WrongChannelNameException 
	 */
	public Channel getChannel (String _channelName) throws NonexistantChannelException {
		return this.listChannel.getChannel(_channelName);
	}
	
	/**
	 * add a subChannel to THIS channel using _channel.name value.
	 * The _channel.absoluteName is automatically set.
	 * parentChannel is automatically set.
	 * if a channel with the same name already exists, it will be remplaced
         * 
         * e.g: "general/myChannel/channel1"
         * <code>
         *       Channel generalChannel = Dispatcher.getChannel(Channel.create("general"));
         *       generalChannel.addChannel("myOtherChannel") ; // add the channel "general/myOtherChannel"
         * </code>
         * notice that Dispatcher.addChannel(Channel.create("general/myOtherChannel")) do the same thing and is syncronized !
	 * @param _channel
	 */
	public void addChannel (Channel _channel) {
		_channel.setParentChannel(this);
		this.listChannel.addChannel(this.getAbsoluteName(), _channel);
	}
	
	/**
	 * Remove the _channel to the ListChannel
	 * _channel is find using the name.
     * e.g: general/myChannel/channel1
     *      if you are into the Channel 'general/myChannel' and you want to remove the channel 'channel1'
	 *      Dispatcher.getChannel(Channel.create("general/myChannel")).remove(channel1)
     *      Or easier and synchronised using the dispatcher: Dispatcher.remove("general/myChannel/channel1")
	 * @param _channel
	 * @throws NonexistantChannelException 
	 */
	public void removeChannel (String _channel) throws NonexistantChannelException {
		this.listChannel.removeChannel(_channel);
	}
	
	/**
	 * @return the parent channel of this channel. null if it's a first-level channel (no parents)
	 */
	public Channel getParentChannel () {
		return this.parentChannel ;
	}
	
	/**
	 * UNSAFE OPERATION. ONLY DISPATCHER OBJECT SHOULD USE IT
	 * set the parentChannel = _parentChannel
	 * @param _parentChannel
	 */
	public void setParentChannel (Channel _parentChannel) {
		this.parentChannel = _parentChannel ;
	}
	
	/**
	 * @return the number of waiter inside this channel.
	 */
	public int getNumberOfWaiter () {
		return this.waiters.size();
	}

	/**
	 * @return the waiter inside this channel.
	 */
	public HashMap<String, Waiter> getWaiters () {
		return this.waiters;
	}
	
	/**
	 * Push the function _functionName with its attributes _attributes to all the user of the current channel
	 * e.g: 
	 * <code>
	 * 		Dispatcher.push("error", "message1", "foo") will push the message {functionName: "error", arguments: ["message1", "foo"]}
	 * 		//Javascript api will automatically launch the function : avriServer.functions.error ("message1", "foo") ;
	 * </code>		
	 * Also work with Dispatcher.push("myJavascriptSpaceName.error", "message1", "foo") : avriServer.functions.myJavascriptSpaceName.error ("message1", "foo")
	 * @param _channel
	 * @param _message
	 * @throws JSONException 
	 */
	public void push(String _functionName, Object... _arguments) {
		for (Entry<String, Waiter> entry : this.waiters.entrySet()) {
			entry.getValue().push(_functionName, _arguments);
		}
	}
	
	/**
	 * Remove the waiter _waiter of this channel.
	 * @param _waiter
	 */
	public void removeWaiter (Waiter _waiter) {
		this.waiters.remove(_waiter.getPublicId());
	}
	
	/**
	 * Clean the empty sub-channels of the channel.
	 */
	public void cleanVacantChannel () {
		this.listChannel.cleanVacantChannel();
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((absoluteName == null) ? 0 : absoluteName.hashCode());
		return result;
	}


	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Channel other = (Channel) obj;
		if (absoluteName == null) {
			if (other.absoluteName != null)
				return false;
		} else if (!absoluteName.equals(other.absoluteName))
			return false;
		return true;
	}
	
}
